﻿using CSharpApp;
using SharpIM.Http;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace SharpIM
{
    public class HttpServer
    {
        public HttpApiHandler ApiHandler { get; private set; }
        public VirtualFileSystem WwwVfs { get; private set; }
        public VirtualFileSystem RootVfs { get; private set; }
        public string RootPath { get; set; }
        Dictionary<string, List<HttpApiClassDescriptor>> apiTypes = new Dictionary<string, List<HttpApiClassDescriptor>>();
        internal static int maxHttpBodySize = 4 * 1024 * 1024;
        internal static int maxHttpHeaderSize = 32 * 1024;
        public HttpServer()
        {
            EnableInsecreRequestsUpgrade = false;
            RootPath = AppDomain.CurrentDomain.BaseDirectory;
            RootVfs = new VirtualFileSystem(RootPath);
            WwwVfs = RootVfs.CreateChild("www");
            LoadAssembly(typeof(HttpServer).Assembly);
            foreach (var item in RootVfs.EnumerateFiles("*.dll"))
            {
                try
                {
                    var asm = Assembly.LoadFile(item);
                    LoadAssembly(asm);
                }
                catch (Exception)
                {

                }
            }
        }
        void LoadAssembly(Assembly asm)
        {
            try
            {

                var types = asm.GetTypes();
                types = types.Where(x => typeof(HttpApi).IsAssignableFrom(x) && x.IsClass && !x.IsAbstract).ToArray();
                foreach (var type in types)
                {
                    if (type.Name.EndsWith("Api"))
                    {
                        var descriptor = new HttpApiClassDescriptor(type);
                        if (!apiTypes.ContainsKey(descriptor.RoutePathValue))
                            apiTypes.Add(descriptor.RoutePathValue, new List<HttpApiClassDescriptor> { descriptor });
                        else
                        {
                            if (!apiTypes[descriptor.RoutePathValue].Any(x => x.ApiClassType == type))
                            {
                                apiTypes[descriptor.RoutePathValue].Add(descriptor);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {

            }

        }
        /// <summary>
        /// upgrade http requests to https requests, default is True
        /// </summary>
        public bool EnableInsecreRequestsUpgrade { get; set; }
        public bool SupressErrorDetail { get; private set; }

        public HttpResponse ProcessRequest(HttpRequest request)
        {
            HttpResponse response;
            if (!Guard(request, out response))
            {
                var html = "index.html";
                if (!WwwVfs.FileExists(html))
                {
                    WwwVfs.Write(html, HttpErrors.Default);
                }
                if (!this.apiTypes.Any())
                {
                    response = request.CreateFileResponse(WwwVfs.GetFullName(html));
                }
                else
                {
                    var m = FindApi(request);
                    if (m == null)
                    {
                        response = request.CreateResponse(404, HttpErrors.E404);
                    }
                    else
                    {
                        object ret = null;
                        bool noContent = m.ReturnType == typeof(void);
                        try
                        {
                            ret = m.Execute(request);
                            response = request.CreateJsonResponse(noContent ? 204 : 200, ret);
                        }
                        catch (Exception ex)
                        {
                            response = this.CreateErrorResponse(request, ex);
                        }
                    }
                }
            }
            response.SetCookie("a", "b");
            return response;
        }

        private HttpApiMethodDescriptor FindApi(HttpRequest request)
        {
            HttpApiMethodDescriptor m;
            var route = request.Route;
            if (route == null)
                return null;
            else
            {
                var apiTypes = this.apiTypes
                    .Where(x => x.Key.Equals(route.ClassRouteName, StringComparison.OrdinalIgnoreCase))
                    .SelectMany(x => x.Value);
                foreach (var item in apiTypes)
                {
                    if (item.IsRoutePathMatch(request, out m))
                    {
                        return m;
                    }
                }
                return null;
            }
        }

        public HttpResponse CreateErrorResponse(HttpRequest request, Exception ex)
        {
            HttpResponse response = null;
            if (ex is FileNotFoundException)
            {
                response = request.CreateResponse(404, !this.SupressErrorDetail ? new ErrorFormatter().Format(ex) : HttpErrors.E404);
            }
            else if (ex is UnauthorizedAccessException)
            {
                response = request.CreateResponse(403, !this.SupressErrorDetail ? new ErrorFormatter().Format(ex) : "");
            }
            else
            {
                response = request.CreateResponse(500, !this.SupressErrorDetail ? new ErrorFormatter().Format(ex) : "");
            }
            return response;
        }

        public HttpResponse CreateErrorResponse(HttpContext httpContex, Exception ex)
        {
            HttpResponse response = httpContex.Response;
            response.Error = ex;
            HttpRequest request = httpContex.Request;
            if (response.Output != null)
            {
                response.Output.Dispose();
                response.Output = null;
            }
            if (ex is FileNotFoundException)
            {
                response = request.CreateResponse(404, !this.SupressErrorDetail ? new ErrorFormatter().Format(ex) : HttpErrors.E404);
            }
            else if (ex is UnauthorizedAccessException)
            {
                response = request.CreateResponse(403, !this.SupressErrorDetail ? new ErrorFormatter().Format(ex) : "");
            }
            else
            {
                response = request.CreateResponse(500, !this.SupressErrorDetail ? new ErrorFormatter().Format(ex) : "");
            }
            return response;
        }

        bool Guard(HttpRequest request, out HttpResponse response)
        {
            if (request.IsSecure && request.Url.Scheme == Uri.UriSchemeHttp)
            {
                response = request.CreateResponse(500, "Your send a http request over https connection");
                return true;
            }
            else if (!request.IsSecure && request.Url.Scheme == Uri.UriSchemeHttps)
            {
                response = request.CreateResponse(500, "Your send a https request over http connection");
                return true;
            }

            if (!request.IsSecure && request.UpgradeInsecureRequests && EnableInsecreRequestsUpgrade)
            {
                var url = request.Url;
                var location = "https://" + url.GetComponents(url.IsDefaultPort ? UriComponents.Host : UriComponents.HostAndPort | UriComponents.PathAndQuery, UriFormat.SafeUnescaped);
                response = request.CreateRedirectResponse(307, location);
                return true;
            }
            response = null;
            return false;
        }

    }
}
